//tcp_server.cc
#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include"thread_pool.hpp"
#include"Task.hpp"
using namespace Mango;

//真正和用户通信用的是accpet返回的新的套接字,listen套接字只负责将新的套接字从底层获取上来
//所以TCP网络通信是文件描述符在不断增多的场景
void ServiceIO(int new_sock) //提供服务的函数
{
        //提供服务,我们是一个死循环
        while(true)
        {
            //先从客户端中读取,读完之后再写回给客户端
            char buffer[1024];
            memset(buffer, 0, sizeof(buffer));
            //从new_sock读取,读到buffer,期望读取sizeof(buffer)-1个字节
            ssize_t s = read(new_sock, buffer, sizeof(buffer)-1);
            if(s > 0) //读取成功
            {
                buffer[s] = 0; //将获取的内容当成字符串,末尾置\0
                std::cout << "client# " << buffer << std::endl;

                std::string echo_string = ">>>server<<<, ";
                echo_string += buffer;
                
				//把内容重新写回去
                write(new_sock, echo_string.c_str(), echo_string.size());
            }
            else if(s == 0) //对端把连接断了
            {
                std::cout << "client quit ..." << std::endl;
                break;
            }
            else 
            {
                std::cerr << "read error" << std::endl;
                break;
            }
        }
}
void *HandlerRequest(void *args)
{
    pthread_detach(pthread_self());//线程分离
    int new_sock = *(int*)args;
    delete (int*)args;
    //用完了把自己的套接字关了就可以
    ServiceIO(new_sock);//提供服务
    close(new_sock);//关闭文件描述符
}
void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}
// ./tcp_server tcp_port(服务器server要绑定的端口)
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    //1.创建套接字
    int listen_sock = socket(AF_INET,SOCK_STREAM,0);
    if(listen_sock<0)
    {
        std::cerr<<"socket error:"<<errno<<std::endl;
        return 2;
    }
    //2.绑定
    struct sockaddr_in local;
    memset(&local,0,sizeof(local));
    //填充结构体内容
    local.sin_family = AF_INET;//协议家族:  选择IPV4
    local.sin_port = htons(atoi(argv[1]));//字符串端口号转为整数端口号(主机序) -> 转为网络字节序
    local.sin_addr.s_addr = INADDR_ANY;//云服务器不建议绑定固定的IP

    //绑定
    if(bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cerr << "bind error: " << errno << std::endl;
        return 3;
    }
    //监听
    const int back_log = 5;
    if(listen(listen_sock, back_log) < 0)
    {
        std::cerr << "listen error" << std::endl;
        return 4;
    }

    //版本1:
    //获取连接
    //对外提供服务
    /*
    for( ; ; ) 
    {    
        struct sockaddr_in peer;//记录远端(客户端)的网络的相关属性
        socklen_t len = sizeof(peer);
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);//获取客户端谁来访问我
        if(new_sock < 0)//获取新链接失败,继续下一次获取
        {
            continue;
        }
        //连接成功
        //对端的网络相关信息被放到了peer结构体里面
        
        //为了方便打印观察是谁连接的我
        uint16_t cli_port = ntohs(peer.sin_port);//客户端的端口->peer.sin_port拿到的是网络字节序->需要用ntohs函数转为主机序
        std::string cli_ip = inet_ntoa(peer.sin_addr);//客户端的IP地址->peer.sin_addr拿到的是4字节IP地址->用inet_ntoa转为字符串IP 
        std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port <<"]# " << new_sock << std::endl;
        
        //提供服务
        ServiceIO(new_sock);
    }
    */
    
    //版本2:多进程版本-写法1:
    
    signal(SIGCHLD, SIG_IGN); //在Linux中父进程忽略子进程的SIGCHLD信号，当子进程退出时,会直接释放子进程的资源
    //服务端对外提供服务的代码
    for (;;)
    {
        // struct sockaddr_in peer;//记录远端(客户端)的网络的相关属性
        // socklen_t len = sizeof(peer);
        // int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);//获取客户端谁来访问我
        // if(new_sock < 0)//获取新链接失败,继续下一次获取
        // {
        //     continue;
        // }

        // //提供服务
        // //为了方便打印观察是谁连接的我
        // uint16_t cli_port = ntohs(peer.sin_port);//客户端的端口->拿到的是网络字节序->需要转为主机序
        // std::string cli_ip = inet_ntoa(peer.sin_addr);//客户端的IP地址 -> 拿到的是4字节IP地址->转为字符串IP 
        // std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port <<"]# " << new_sock << std::endl;
        
        // //创建子进程帮我们执行任务
        // pid_t id = fork();
        // if (id == 0)//child
        // { 	
        //     //处理请求
        //     close(listen_sock);
        //     ServiceIO(new_sock);
        //     close(new_sock);
        //     exit(0); //子进程提供完服务自动退出
        // }
        // //父进程关闭new_sock
        // close(new_sock);
    }
    
    

    //写法2:
    //服务端对外提供服务的代码
    /*
    for( ; ; ) 
    {    
        struct sockaddr_in peer;//记录远端(客户端)的网络的相关属性
        socklen_t len = sizeof(peer);
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);//获取客户端谁来访问我
        if(new_sock < 0)//获取新链接失败,继续下一次获取
        {
            continue;
        }
        //连接成功
        //对端的网络相关信息被放到了peer结构体里面

        //为了方便打印观察是谁连接的我
        uint16_t cli_port = ntohs(peer.sin_port);//客户端的端口->peer.sin_port拿到的是网络字节序->需要用ntohs函数转为主机序
        std::string cli_ip = inet_ntoa(peer.sin_addr);//客户端的IP地址->peer.sin_addr拿到的是4字节IP地址->用inet_ntoa转为字符串IP 
        std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port <<"]# " << new_sock << std::endl;

        //提供服务
        pid_t id = fork();
        if (id < 0) 
        {
            continue;  // 创建失败
        }
        else if (id == 0) // child
        {           
            // 子进程会继承父进程的文件描述符,相当于复制一份,
            // 父子进程的文件描述符互不影响,故关闭子进程的监听套接字文件描述符
            close(listen_sock);

            if (fork() > 0) exit(0);  // 子进程退出,任务交给孙子进程执行	
            
            //这里向后走的进程，其实是孙子进程  爷孙进程不需要等待,
            //孙子进程的父进程退出, 变成孤儿进程,被OS领养
            ServiceIO(new_sock);//孙子进程提供服务
            close(new_sock);          // 使用完就关掉,防止文件描述符泄漏
            exit(0);
        }
        else		//parent
        {                        
            close(new_sock);          // 关闭父进程的newsock,以待重新获取连接
            waitpid(id, nullptr, 0);  // 等待子进程退出
        }
    }
    */

    //写法3:多线程版本
    //服务端对外提供服务的代码
    /*
    for( ; ; ) 
    {    
        struct sockaddr_in peer;//记录远端(客户端)的网络的相关属性
        socklen_t len = sizeof(peer);
        int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);//获取客户端谁来访问我
        if(new_sock < 0)//获取新链接失败,继续下一次获取
        {
            continue;
        }
        //连接成功
        //对端的网络相关信息被放到了peer结构体里面

        //为了方便打印观察是谁连接的我
        uint16_t cli_port = ntohs(peer.sin_port);//客户端的端口->peer.sin_port拿到的是网络字节序->需要用ntohs函数转为主机序
        std::string cli_ip = inet_ntoa(peer.sin_addr);//客户端的IP地址->peer.sin_addr拿到的是4字节IP地址->用inet_ntoa转为字符串IP 
        std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port <<"]# " << new_sock << std::endl;

        //提供服务
        pthread_t tid;
        int* pram = new int(new_sock);
        pthread_create(&tid, nullptr, HandlerRequest, pram);
    }
    */

    //版本4:线程池版本
    //服务端对外提供服务的代码
    
    // for( ; ; ) 
    // {    
    //     struct sockaddr_in peer;//记录远端(客户端)的网络的相关属性
    //     socklen_t len = sizeof(peer);
    //     int new_sock = accept(listen_sock, (struct sockaddr*)&peer, &len);//获取客户端谁来访问我
    //     if(new_sock < 0)//获取新链接失败,继续下一次获取
    //     {
    //         continue;
    //     }
    //     //连接成功
    //     //对端的网络相关信息被放到了peer结构体里面

    //     //为了方便打印观察是谁连接的我
    //     uint16_t cli_port = ntohs(peer.sin_port);//客户端的端口->peer.sin_port拿到的是网络字节序->需要用ntohs函数转为主机序
    //     std::string cli_ip = inet_ntoa(peer.sin_addr);//客户端的IP地址->peer.sin_addr拿到的是4字节IP地址->用inet_ntoa转为字符串IP 
    //     std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port <<"]# " << new_sock << std::endl;

    //     //使用单例模式的线程池
    //     //1. 构建一个任务
    //     Task t(new_sock);
    //     //2. 将任务push到后端的线程池即可,然后主线程继续获取连接
    //     //服务就由后端的线程池提供服务
    //     ThreadPool<Task>::GetInstance()->PushTask(t);
        
    // }
    
    return 0;
}
